/**
 * 
 */
package truerefactor.refactor;

import java.util.ArrayList;
import java.util.List;

import truerefactor.graph.AttributeNode;
import truerefactor.graph.ClassNode;
import truerefactor.graph.CodeGraph;
import truerefactor.graph.CodeNode;
import truerefactor.graph.Edge;
import truerefactor.graph.EdgeType;
import truerefactor.graph.MethodNode;
import truerefactor.graph.TypePair;

/**
 * PullUpMethod - An implementation of the Pull Up Method Refactoring technique.
 * 
 * @author Isaac Griffith
 */
public class PullUpMethod extends Refactoring {

    /**
     * Creates a new PullUpMethod instance
     */
    public PullUpMethod()
    {
        this.name = "Pull-up Method";
        this.description = "";
    }

    /*
     * (non-Javadoc)
     * @see
     * truerefactor.refactor.Refactoring#execute(truerefactor.graph.CodeNode [])
     */
    /**
     * Expects that the first node will be the superclass, and that the
     * remaining nodes follow ClassNode, MethodNode, ClassNode, MethodNode, ...
     * pattern.
     */
    @Override
    public void execute(CodeGraph graph)
    {
        if (operands.isEmpty())
            return;

        MethodNode method = (MethodNode) operands.get(0);
        ClassNode parentClass = method.getParentClass();

        if (parentClass == null)
            return;

        // 1. Compare methods to ensure they are identical
        // TODO figure this out, deal with this during code generation

        // 2. If they have different signatures, change them to be the one you
        // want in the superclass

        // FIXME For now assume they have the same signature

        // 3. create a new method in the superclass, copy the body of one of the
        // methods to it
        // a. if the method depends on or calls another method in both
        // subclases, declare an abstract method in the superclass
        // b. if the method uses a subclass field, use Pull Up Field or Self
        // Encapsulate Field and declare and use an abstract getting method

        MethodNode newMethod = new MethodNode(method.getIdentifier(), method.getCode(), method.getAccessibility());
        List<String> params = method.getParameters();
        List<TypePair> paramInfo = method.getAllParamInfo();
        for (int i = 0; i < params.size(); i++)
        {
            newMethod.addParameter(params.get(i), paramInfo.get(i).getType(), paramInfo.get(i).isPrimitive());
        }

        ClassNode superClass = null;
        for (CodeNode node : parentClass.getParentClasses())
        {
            if (node instanceof ClassNode)
            {
                ClassNode temp = (ClassNode) node;
                Edge tmpEdge = graph.getEdge(temp, parentClass, EdgeType.Generalization);
                if (tmpEdge != null)
                {
                    superClass = temp;
                    break;
                }
            }
        }

        if (superClass == null)
            return; // can't perform refactoring, since there is no superclass

        // verify the superclass does not have the method
        for (CodeNode node : superClass.getMethods())
        {
            if (node.getIdentifier().substring(node.getIdentifier().lastIndexOf(".") + 1)
                    .equals(method.getIdentifier().substring(method.getIdentifier().lastIndexOf(".") + 1)))
            {
                if (node instanceof MethodNode)
                {
                    MethodNode mNode = (MethodNode) node;
                    if (mNode.getParameters().size() == method.getParameters().size())
                        return;
                }
            }
        }

        graph.insert(newMethod);
        graph.insert(new Edge(superClass, newMethod, EdgeType.Method));
        newMethod.setParentClass(superClass);
        superClass.addMethod(newMethod);

        removeSubClassMethods(graph, method, newMethod, superClass);

        // 6. Take a look at the callers of this method to see whether you can
        // change a required type to the superclass
        
        // TODO fix during code generation stuff
    }

    private void removeSubClassMethods(CodeGraph graph, MethodNode method, MethodNode newMethod, ClassNode superClass)
    {
        // 5. Delete all subclass methods until only the superclass method
        // remains
        List<MethodNode> removeList = new ArrayList<MethodNode>();
        for (CodeNode node : superClass.getSubClasses())
        {
            if (node instanceof ClassNode)
            {
                ClassNode subClass = (ClassNode) node;
                for (CodeNode mNode : subClass.getMethods())
                {
                    if (mNode instanceof MethodNode)
                    {
                        MethodNode tempMethod = (MethodNode) mNode;

                        if (tempMethod.equals(method))
                        {
                            removeList.add(tempMethod);
                            break;
                        }
                    }
                }
            }
        }

        for (int i = 0; i < removeList.size(); i++)
        {
            MethodNode mNode = removeList.get(i);
            for (MethodNode assoc : mNode.getAssociatedMethods())
            {
                assoc.removeAssociatedMethod(mNode);
                assoc.addOtherMethod(newMethod);
                newMethod.addOtherMethod(assoc);
            }
            
            for (MethodNode other : mNode.getOtherMethods())
            {
                other.removeOtherMethod(mNode);
                other.addOtherMethod(newMethod);
                newMethod.addOtherMethod(other);
            }
            
            for (AttributeNode assoc : mNode.getAssocAttrs())
            {
                assoc.removeAssociatedClassMethod(mNode);
                assoc.addAssociatedOtherMethod(newMethod);
                newMethod.addOtherAttribute(assoc);
            }
            
            for (AttributeNode other : mNode.getOtherAttributes())
            {
                other.removeAssociatedOtherMethod(mNode);
                other.addAssociatedOtherMethod(newMethod);
                newMethod.addOtherAttribute(other);
            }
            
            mNode.getParentClass().removeMethod(mNode);
            graph.remove(removeList.get(i));
        }
    }
}
